#include "head.h"
#include <asm-generic/socket.h>
#include <cstdlib>
#include <iostream>
#include <sys/epoll.h>
#include <sys/socket.h>

using std::cout;
using std::endl;

#define MAX_SIZE 1000
#define TIMEOUT 1000

int main()
{
    //1. 创建监听套接字
    int sfd = socket(AF_INET, SOCK_STREAM, 0);
    cout << "sfd:" << sfd << endl;
    ERROR_CHECK(sfd, -1, "socket");

    int on = 1;
    int ret = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
    ERROR_CHECK(ret, -1, "setsockopt");
    
    //2. 服务器绑定网络地址
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(8000);
    serveraddr.sin_addr.s_addr = inet_addr("192.168.30.129");

    ret = bind(sfd, 
        (const struct sockaddr*)&serveraddr, sizeof(serveraddr));
    ERROR_CHECK(ret, -1, "bind");

    //3. 进行监听
    ret = listen(sfd, 128);
    cout << "server is listening.\n";

    //4. 创建epoll的实例
    int epfd = epoll_create1(0);
    cout << "epfd " << epfd << endl;
    //5. 监听sfd的读事件
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = sfd;
    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, sfd, &ev);
    ERROR_CHECK(ret, -1, "epoll_ctl");

    //6.在用户态定义一个struct epoll_event的数组
    //该数组用来存储就绪的文件描述符信息
    //calloc会对开辟的空间进行初始化
    struct epoll_event * pArr = 
        (struct epoll_event*)calloc(MAX_SIZE, sizeof(struct epoll_event));

    int nready = 0;
    while(1) {
        //nready = epoll_wait(epfd, pArr, MAX_SIZE, TIMEOUT);
        //第四个参数设置为-1，表示无限等待
        //直到有文件描述符就绪
        cout << ">> enter event loop" << endl;
        nready = epoll_wait(epfd, pArr, MAX_SIZE, -1);
        cout << "nready:" << nready << endl;
        if(0 == nready)  {
            cout << "epoll timeout.\n";
            continue;
        } else if(-1 == nready && errno == EINTR) {
            continue;
        } else if(-1 == nready) {
            perror("epoll_wait");
            return -1;
        } else {
            //nready > 0 的情况, 有文件描述符就绪
            //并且存放在pArr数组的前nready个
            for(int i = 0; i < nready; ++i) {
                int fd = pArr[i].data.fd;

                if(fd == sfd) {//新连接到来的情况
                    struct sockaddr_in clientAddr;
                    socklen_t len = sizeof(clientAddr);
                    int newFd = accept(sfd, (struct sockaddr*)&clientAddr, &len);
                    ERROR_CHECK(newFd, -1, "accept");
                    cout << "newFd " << newFd << endl;
                    cout << "client[" << inet_ntoa(clientAddr.sin_addr) << ":"
                         << ntohs(clientAddr.sin_port) << "] has connected\n";
                    //newFd 是一个新的套接字，代表已经建立好的连接
                    //需要对其进行读事件的监听
                    ev.events = EPOLLIN;//表示监听的是客户端发送数据过来的事件
                    ev.data.fd = newFd;
                    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, newFd, &ev);
                    ERROR_CHECK(ret, -1, "epoll_ctl");

                } else {
                    //已经建立好的连接，要处理数据
                    char buff[1024] = {0};
                    ret = recv(fd, buff, sizeof(buff), 0);
                    //ret返回值为0时，代表的是连接已经断开了
                    //此时会一直触发读事件,会出现无穷打印操作
                    //要解决该问题，应该怎么办？只需要关闭该连接，同时将其
                    //文件描述符从epoll的监听红黑树上删除掉
                    if(0 == ret) {
                        //连接断开了
                        ev.data.fd = fd;
                        //从红黑树上删除监听
                        ret = epoll_ctl(epfd, EPOLL_CTL_DEL, fd, &ev);
                        ERROR_CHECK(ret, -1, "epoll_ctl");
                        close(fd);//关闭该连接
                        cout << "conn " << fd << " has closed.\n";
                        continue;
                    }
                    cout << "conn " << fd << " recv ret " << ret << " bytes.\n";
                    cout << "buff:" << buff << endl;
                    //回显客户端的数据
                    send(fd, buff, strlen(buff), 0);
                }
            }
        }
    }


    free(pArr);
    close(epfd);
    close(sfd);
    return 0;
}
